package challenge;
import java.util.ArrayList;



public class Tabou extends Thread{
	protected Problem pb_;
	public Solution sol;
	public Solution best_solution;
	private ArrayList< ArrayList<Batch> > TabouProd;
	private ArrayList< ArrayList<Batch> > TabouDev;
	private ArrayList< ArrayList<Batch> > ResProd;
	private ArrayList< ArrayList<Batch> > ResDev;
	private int durTabou;
	private long start,end;
	private long tpsExec;
	private int thread;
	private int myThread;

	Tabou (Problem pb, int tabou,int pthread,int pmyThread,long tps, int limit){ 
		TabouProd = new ArrayList< ArrayList<Batch> >(); // Liste Tabou de la sol.sequenceProductionMT
		TabouDev = new ArrayList< ArrayList<Batch> >(); // Liste Tabou de la sol.sequenceDeliveryMT
		ResProd = new ArrayList< ArrayList<Batch> >();
		ResDev = new ArrayList< ArrayList<Batch> >();
		durTabou = tabou; // durée Tabou
		pb_=pb;
		thread = pthread;
		myThread=pmyThread;
		run(tps); // lancements des Threads
	}

	public void run(long tps) {
		sol = new Solution(pb_) ; // construction de la solution courante du thread
		best_solution= new Solution(pb_) ; // construction de la meilleur solution
		tpsExec=tps;  // temps d'execution alloué au thread
		sol.randomize(thread,myThread); // on genère une première solution aléatoire
		sol.evaluate() ; // on évalue la solution
		// on optimise et on affiche la meilleure solution avec optmisier
		//optimiser retourne best_solution
		start=System.currentTimeMillis();
		System.out.println("solution="+optimiser().toString()) ; 
	}

	public void voisinage(){
		// on cherche les voisins de la liste de production et on retiens le meilleur
		voisinage_production(); 
		sol.evaluate();
		// on cherche les voisins de la liste de transport et on retiens le meilleur
		voisinage_delivery();


	}

	void reset_sol(){
		sol.reset();// on vide la solution
		sol.randomize(thread,myThread);// on genère la solution aléatoire
		sol.evaluate();// on évalue la solution
	}


	void voisinage_production(){
		if(modif_prod_list()){// si on peut modifier
			modif_prod_list();// on modifie la liste de production tant que c'est possible
		}
		else{// sinon on tente d'ajouter ou de supprimer un lot
			modif_size_prod_list();
			modif_prod_list();
		}
	}

	void voisinage_delivery(){

		if(modif_deliv_list()){// si on peut modifier la liste de transport
			modif_deliv_list(); // on modifie tant que l'on peut
		}
		else{
			modif_size_deliv_list();//sinon on tente d'ajouter ou de supprimer un élement
			modif_deliv_list();
		}
	}

	boolean modif_prod_list(){  
		int step = pb_.getNp()/20;
		boolean modif=false;
		while(step>0){
			while(modif_prod_list_step(step));
			step-=5;
		}
		while(modif_prod_list_step(2));
		while(modif_prod_list_step(1));
		return modif;
	}

	boolean modif_prod_list_step(int step) 
	{
		boolean modif =false; // ce booléen passe a true lorsqu'une amélioration est effectuée
		double best_vois_step = sol.evaluation; // la meilleur solution connue du voisinage est la sol courante 
		int best1=-1,best2=-1; // index de la meilleur solution du voisinage
		if(sol.productionSequenceMT.size()>1){// si la liste contien plus d'un lot on fais des changements
			for(int i=0; i<sol.getProductionSequenceMTSize();++i){
				if(sol.getProductionBatchSize(i)>step){ // si le job contient plus d'un produit
					sol.setProductionBatchSize(i, sol.getProductionBatchSize(i)-step); // on enlève step produit du lot
					for(int j=0;j<sol.getProductionSequenceMTSize();++j){
						sol.setProductionBatchSize(j, sol.getProductionBatchSize(j)+step); // on l'assigne step produits à un autre lot 
						sol.evaluate() ;	// on estime ce voisin
						if(sol.evaluation<best_vois_step && !TabouProd.contains(sol.productionSequenceMT) && i!=j)// si ce mouvement est non tabou et si ce voisin a la meilleure fitness
						{
							modif=true;
							best1=i;
							best2=j;
							best_vois_step  = sol.evaluation;
						}
						// on re-transforme ce voisin en la solution en construction
						sol.setProductionBatchSize(j, sol.getProductionBatchSize(j)-step); 
					}
					sol.setProductionBatchSize(i, sol.getProductionBatchSize(i)+step);
				}
			}
		}
		// si il y a eu modification on la reconstruit pour obtennir le meilleur voisin
		if(best1!=best2 && best1!=-1 && best2!=-1 && modif == true){
			sol.setProductionBatchSize(best1, sol.getProductionBatchSize(best1)-step);
			sol.setProductionBatchSize(best2, sol.getProductionBatchSize(best2)+step);
			sol.evaluate();}
		return modif;
	} 

	boolean modif_deliv_list(){ 
		int step = pb_.getNp()/20;
		boolean modif=false;
		while(step >0)
		{
			while(modif=modif_deliv_list_step( step));
			step-=5;
		}
		while(modif=modif_deliv_list_step( 2));
		while(modif=modif_deliv_list_step( 1));
		return modif;
	}

	boolean modif_deliv_list_step(int step)
	{
		boolean modif =false; // ce booléen passe a true lorsqu'une amélioration est effectuée
		double best_vois_step = sol.evaluation; // la meilleur solution connue du voisinage est la sol courante 
		int best1=-1,best2=-1; // index de la meilleur solution du voisinage
		if(sol.deliverySequenceMT.size()>1){// si la liste contien plus d'un lot on fais des changements
			for(int i=0; i<sol.deliverySequenceMT.size();++i){
				if(sol.getDeliveryBatchSize(i)>step){ // si le job contient plus d'un produit
					sol.setDeliveryBatch(i, sol.getDeliveryBatchSize(i)-step); // on enlève step produit du lot
					for(int j=0;j<sol.deliverySequenceMT.size();++j){
						if(sol.getDeliveryBatch(j).quantity + step <= pb_.transporter.capacity)
						{ 
							sol.setDeliveryBatch(j, sol.getDeliveryBatchSize(j)+step);// on l'assigne step produits à un autre lot
							sol.evaluate() ;	// on estime ce voisin
							if(sol.evaluation<best_vois_step && !TabouProd.contains(sol.productionSequenceMT) && i!=j)// si ce mouvement est non tabou et si ce voisin a la meilleure fitness
							{
								modif=true;
								best1=i;
								best2=j;
								best_vois_step  = sol.evaluation;
							}
							// on re-transforme ce voisin en la solution en construction
							sol.setDeliveryBatch(j, sol.getDeliveryBatchSize(j)-step);
						}}
					sol.setDeliveryBatch(i, sol.getDeliveryBatchSize(i)+step);
				}
			}
		}
		// si il y a eu modification on la reconstruit pour obtennir le meilleur voisin
		if(best1!=best2 && best1!=-1 && best2!=-1 && modif == true){
			sol.setDeliveryBatch(best1, sol.getDeliveryBatchSize(best1)-step);
			sol.setDeliveryBatch(best2, sol.getDeliveryBatchSize(best2)+step);
			sol.evaluate();}
		return modif;
	} 

	boolean modif_size_prod_list(){
		boolean modif =false;
		sol.evaluate();
		ArrayList<Batch> tmpProductionSequenceMT = new ArrayList<Batch>(sol.productionSequenceMT);//array stockant la valeur actuelle de la liste de prod de sol  
		ArrayList<Batch> tmpAdd = new ArrayList<Batch>();//array stockant la valeur de la liste de prod de sol après ajout d'un lot
		ArrayList<Batch> tmpSup = new ArrayList<Batch>();//array stockant la valeur de la liste de prod de sol après suppression d'un lot
		double evalAdd=1000000000, evalSup=100000000;// valeur initiale de l'évalutation de add et supp
		// -------------------------------------------------- AJOUT ---------------------------------------------------------------------------------  //
		if(sol.productionSequenceMT.size()+1<(myThread+1)*(pb_.np/thread)){ //si l'ajout d'un lot reste dans le domaine de recherche du thread
			for(int a =0; a<sol.productionSequenceMT.size();a++){
				tmpAdd.add(0,new Batch(pb_.np/sol.productionSequenceMT.size()));}
			int total=pb_.getNp()%(sol.productionSequenceMT.size());
			if(total==0){
				for(int j=0; j<sol.productionSequenceMT.size();++j){
					if (sol.productionSequenceMT.get(j).quantity>1){
						sol.productionSequenceMT.set(j, new Batch(sol.productionSequenceMT.get(j).quantity-1));
						total++;
						break;
					}
				}
			}
			tmpAdd.add(new Batch(total)); // Dans le cas ou la division entière ne permette pas d'atteindre 100 produits lors du remplissage
			sol.productionSequenceMT=Solution.cloneA(tmpAdd); // On stock le tableau redimensionné dans la solution courante pour évaluation
			sol.evaluate();// on évalue la nouvelle solution
			evalAdd=sol.evaluationManufacturer; 	// on stock l'évaluation
			tmpAdd=Solution.cloneA(sol.productionSequenceMT); // on stocke la nouvelle liste
		}
		sol.productionSequenceMT=Solution.cloneA(tmpProductionSequenceMT);// on reconstruit la sol initiale 
		// -------------------------------------------------- SUPPRESSION ---------------------------------------------------------------------------------  //
		//si la suppression d'un lot reste dans le domaine du thread
		if(sol.productionSequenceMT.size()-1>=myThread*(pb_.np/thread)+1 && sol.productionSequenceMT.size()>2){ 
			int tmp = sol.productionSequenceMT.get(0).quantity; // on stocke la quantité du lot 0
			sol.delFirstProduction();
			int i =0;
			while(tmp>0){// on répartit de facon homogène 
				tmp-=1;
				sol.setProductionBatchSize(i, sol.getProductionBatchSize(i)+1);
				++i;
				if(i>=sol.productionSequenceMT.size()){
					i=0;
				}
			}
			sol.evaluate();// on évalue la nouvelle solution
			evalSup=sol.evaluationManufacturer;//on stock l'évaluation
			tmpSup=Solution.cloneA(sol.productionSequenceMT);//on stock la nouvelle liste
		}
		sol.productionSequenceMT=Solution.cloneA(tmpProductionSequenceMT);// on reconstruit la sol initiale
		// si on ameliore la solution courante si la suppression est meilleur que l'ajout
		if(evalSup<evalAdd && !TabouProd.contains(sol.productionSequenceMT)){ 
			modif=true;
			sol.productionSequenceMT = Solution.cloneA(tmpSup); // on change la liste courante pour la nouvelle
		}
		// si on ameliore la solution courante si l'ajout est meilleur que l'ajout
		else if(evalSup>evalAdd && !TabouProd.contains(sol.productionSequenceMT)){
			modif=true;
			sol.productionSequenceMT = Solution.cloneA(tmpAdd); // on change la liste courante pour la nouvelle
		}
		System.out.println(sol.toString());
		sol.evaluate();
		return modif;
	}

	boolean modif_size_deliv_list(){
		boolean modif =false;
		//on stocke la liste de transport actuelle et celle issue des ajouts et suppression
		ArrayList<Batch> tmpDeliverySequenceMT = new ArrayList<Batch>(sol.deliverySequenceMT);
		ArrayList<Batch> tmpAdd = new ArrayList<Batch>();
		ArrayList<Batch> tmpSup = new ArrayList<Batch>();
		double evalAdd=1000000000, evalSup=100000000;
		//si la liste de transport a moins de lots que le nombre de produit total
		if(sol.deliverySequenceMT.size()<pb_.np){
			sol.addDeliveryFirst(0);// on ajoute un lot
			int i =0;
			boolean equi=true;
			// on itère à travers la liste pour trouver un un produit à inscrire dans le lot 0
			while( i<sol.deliverySequenceMT.size() && equi){
				if(sol.getDeliveryBatchSize(i)>=2){
					sol.setDeliveryBatch(0, sol.getDeliveryBatchSize(0)+1);
					sol.setDeliveryBatch(i, sol.getDeliveryBatchSize(i)-1);
					equi=false;
				}
				++i;
			}
			sol.evaluate();// on évalue la nouvelle solution
			evalAdd=sol.evaluation;//on stocke la valeur de l'évaluation
			tmpAdd=Solution.cloneA(sol.deliverySequenceMT); // on stocke la liste modifiée
		}
		sol.deliverySequenceMT = Solution.cloneA(tmpDeliverySequenceMT);
		// si la suppression d'un lot n'entraine pas le depassement de capacité des transports
		if((sol.deliverySequenceMT.size()-1)*pb_.transporter.capacity>=pb_.np){
			int tmp = sol.deliverySequenceMT.get(0).quantity; // on stock la valeur du lot 0
			sol.delFirstDelivery();// on supprime le lot 0
			int i =0;
			while(tmp>0){// on répartit de facon homogène les produits du lot 0
				if(pb_.transporter.capacity-sol.getDeliveryBatchSize(i)>0 && tmp>0){
					tmp-=1;
					sol.setDeliveryBatch(i, sol.getDeliveryBatchSize(i)+1);
				}
				++i;
				if(i>=sol.deliverySequenceMT.size()){
					i=0;
				}
			}
			sol.evaluate();// on évalue la nouvelle solution
			evalSup=sol.evaluation;//on stocke la valeur de l'évaluation
			tmpSup=Solution.cloneA(sol.deliverySequenceMT);// on stocke la liste modifiée
			sol.deliverySequenceMT=Solution.cloneA(tmpDeliverySequenceMT);
		}
		if(evalSup<evalAdd && !TabouDev.contains(sol.deliverySequenceMT)){
			modif=true;
			sol.deliverySequenceMT = Solution.cloneA(tmpSup);
			sol.evaluate();
		}
		else if(evalSup>evalAdd && !TabouDev.contains(sol.deliverySequenceMT)){
			modif=true;
			sol.deliverySequenceMT = Solution.cloneA(tmpAdd);
			sol.evaluate();
		}
		return modif;
	}


	public Solution optimiser(){ 
		double best=0;
		int ameliore_solution = -1;   // indique l'iteration o� l'on a am�lior� la solution
		int iter_courante=0;
		double best_eval = sol.evaluation;
		ArrayList<Batch> temp_ = new ArrayList<Batch>();
		end =System.currentTimeMillis();
		while(end-start<tpsExec )
		{
			if(ameliore_solution+10 <iter_courante)
			{
				reset_sol();
				ameliore_solution=iter_courante;
			}
			end =System.currentTimeMillis();
			voisinage() ;  
			System.out.print("\r[");
			for(int i=0;i<tpsExec;i+=(tpsExec/25))
			{
				if(i<end-start)
					System.out.print("-");
				else
					System.out.print(" ");
			} 

			System.out.print("]" + " " + (int)(  (float)(end-start)/tpsExec *100 )+ "%" );
			sol.evaluate();
			// valeur de la fitness apres le mouvement
			if(sol.evaluation < best_eval && sol.evaluation >0)            // si on améliore le plus petit minimum rencontré
			{                                           // alors on l'enregistre dans 'best_solution'
				best_eval = sol.evaluation;          // on mets à jour 'best_eval'
				best_solution.copy(sol);// on enregistre la solution corante comme best_solution
				best_solution.evaluate();
				ameliore_solution = iter_courante;      // on indique que l'amélioration à eu lieu à cette itération
				best =end-start;
			}
			if (TabouProd.size()>durTabou-1) TabouProd.remove(0); // Suppression du premier element
			temp_.clear();
			temp_=sol.productionSequenceMT;
			TabouProd.add(Solution.cloneA(temp_) ); // Ajout d'un élément en fin
			if (TabouDev.size()>durTabou) TabouDev.remove(0);
			TabouDev.add(sol.deliverySequenceMT);
			iter_courante++;
			ResProd.add(sol.productionSequenceMT);
			ResDev.add(sol.deliverySequenceMT);
			//	System.out.println("fin Boucle");
		}
		System.out.println("best time  : "+best);
		return best_solution;
	}
}
// FIN de classe
